import {PluginCommand} from "./PluginCommand";
import {ElementType} from "../modeling/TypeDefine";
import {PluginCADToolFunctionType, Point} from "../../nodeApp/static/lib/crowncad-plugin-sdk-0.0.1";
import {Vector3} from "three";

class WrapFramesDesignCommand extends PluginCommand {
  constructor() {
    super();
    this.modelParams = null;
  }

  /**
   * 内核服务接口
   * @param serviceName 服务名
   * @param needDetail  是否需要明细
   * @param featureName 自定义特征名
   * @param params      参数
   * @returns {Promise<{elements: *[], name: *, entityId: number, id: *, error: number}>}
   */
  async getIncrementServiceByName(serviceName, needDetail, featureName, params) {
    let ret = null;
    let elements = [];
    let entityId = 0;
    // 执行内核接口调用
    switch (serviceName) {
      case "copySurface":
        ret = await this.command.surface.copySurfaceIncrement(params, "");
        break;
      case "offsetSurface":
        ret = await this.command.surface.offsetSurfaceIncrement(params, "");
        break;
      case "createPlaneByDistance":
        ret = await this.command.datum.createPlaneByDistanceIncrement(params, "");
        break;
      case "createPointByPoint":
        ret = await this.command.datum.createPointByPointIncrement(params, featureName);
        break;
      case "createPointByProjectionPoint":
        ret = await this.command.datum.createPointByProjectionPointIncrement(params, featureName);
        break;
      case "cutSplit":
        ret = await this.command.solid.cutSplitIncrement(params, "");
        break;
      case "loftSurface":
        ret = await this.command.surface.loftSurfaceIncrement(params, "");
        break;
      case "knitSurface":
        ret = await this.command.surface.knitSurfaceIncrement(params, "");
        break;


      case "createCutSplit":
        ret = await this.command.solid.createCutSplitIncrement(params, "");
        break;

      case "thicken":
        ret = await this.command.solid.thickenIncrement(params, featureName ?? "");
        break;
      case "booleanSubtract":
        ret = await this.command.solid.booleanSubtractIncrement(params, featureName);
        break;
      case "setEntityVisible":
        ret = await this.command.common.setEntityVisibleIncrement(params);
        break;
    }
    // 执行失败返回错误
    if (ret.code !== 200) {
      throw "生成失败";
    }
    // 如果无需明细数据则直接返回，主要针对 接口setEntityVisible
    if (!needDetail) {
      return {};
    }
    // 其它接口特征操作失败
    if (ret.data?.modelData?.error || !ret.data?.modelData?.currentFeature) {
      throw serviceName;
    }
    // 获取明细数据
    let [feature] = ret.data.modelData.currentFeature;
    // 基准面需要从feature中获取，addElements是图元数据
    if (feature.type === "DatumPlane") {
      let eles = await this.getQueryServiceByType("getElementsByFeatureId", feature.id, [])
      elements = eles.allElements;
      let plane = eles.allElements.find(j => j.type === ElementType.DATUMPLANE)
      entityId = plane.params?.entityRefId;
    } else if (feature.type === "DatumPoint") {
      let eles = await this.getQueryServiceByType("getElementsByFeatureId", feature.id, [])
      elements = eles.allElements;
      entityId = elements[0]?.entityId;
    } else {
      let eleData = ret.data?.modelData?.data?.addElements;
      if (eleData && eleData.length > 0) {
        // 调用cc端界面查询
        let {
          allElements,
          defaultEntityId
        } = await this.getQueryServiceByType("getElementsByIds", [...eleData.map(j => j.id)], [])
        elements = allElements;
        entityId = defaultEntityId;
      }
    }
    return {
      error: 0,
      id: feature?.id,
      name: feature?.name,
      elements,
      entityId
    }
  }

  /**
   * cc端界面元素查询
   * @param typeName    查询方法名
   * @param params      查询参数
   * @param returnType  元素类型过滤
   * @returns {Promise<{defaultEntityId: *[], allElements: *[]}>}
   */
  async getQueryServiceByType(typeName, params, returnType) {
    returnType = returnType ?? [];
    let serviceRet = null;
    let ret = {
      allElements: [],
      defaultEntityId: [],
      entities: []
    }
    switch (typeName) {
      case "getElementsByEntityId":
        serviceRet = await this.query.getElementsByEntityId(params)
        break;
      case "getElementsByIds":
        serviceRet = await this.query.getElementsByIds(params);
        break;
      case "getElementsByFeatureNames":
        serviceRet = await this.query.getElementsByFeatureNames(params)
        break;
      case "getElementsByFeatureId":
        serviceRet = await this.query.getElementsByFeatureId(params)
        break;
      case "getEntitiesByFeatureIds":
        serviceRet = await this.query.getEntitiesByFeatureIds(params)
        break;
      case "getEntitiesByFeatureNames":
        serviceRet = await this.query.getEntitiesByFeatureNames(params)
        break;
    }
    if (serviceRet === null || serviceRet.code !== 200 || !serviceRet.data) {
      throw "生成失败"
    }

    // 解析元素数据
    if (typeName.indexOf("getElements") >= 0) {
      ret.allElements = serviceRet.data.elements ?? [];
      if (returnType.length > 0) {
        ret.allElements = ret.allElements.filter(ele => returnType.find(type => type === ele.type))
      }
      if (ret.allElements.length > 0) {
        ret.defaultEntityId = ret.allElements[0].params?.entityRefId;
        ret.allElements.forEach(ele => {
          ele.entityId = ele.params?.entityRefId;
          if (ele.type === ElementType.FACE) {
            // 组织面数据，根据index和position，并返回默认法向
            ele.points = [];
            ele.defaultNormal = {x: ele.params.normal[0], y: ele.params.normal[1], z: ele.params.normal[2]}
            this.getTrianglesByFace([ele])
          }
          if (ele.type === ElementType.EDGE || ele.type === ElementType.CURVE) {
            // 线数据
            ele.lineLength = ele.params.length;
            ele.sPoint = {
              x: ele.params.startPoint[0],
              y: ele.params.startPoint[1],
              z: ele.params.startPoint[2]
            }
            ele.ePoint = {
              x: ele.params.endPoint[0],
              y: ele.params.endPoint[1],
              z: ele.params.endPoint[2]
            }
            ele.mPoint = {
              x: ele.params.middlePoint[0],
              y: ele.params.middlePoint[1],
              z: ele.params.middlePoint[2]
            }
          }
          if (ele.type === ElementType.VERTEX) {
            // 顶点
            ele.point = ele.params.position
          }
        })
      }
    }
    // 解析实体数据
    if (typeName.indexOf("getEntities") >= 0) {
      ret.entities = serviceRet.data.entities ?? [];
      ret.defaultEntityId = ret.entities[0]?.id
    }
    return ret;
  }

  async test() {

  }

  /**
   * 生成基础包框模型
   * @returns {Promise<{copyOutsideFaceRet: *[], solidRet: {elements: *[], name: *, entityId: number, id: *, error: number}}>}
   */
  async createFrame() {
    let hideEntities = [];
    let planeId = this.modelParams.frameDatum.elements[0].id;
    let frameDatumOrigin = this.modelParams.frameDatum.elements[0].params.origin;
    let cutDatumPlaneParams = {
      planeId,
      invertNormal: 0,
      paralDis: 10,
      reverse: 0,
    };
    // 复制需要截取的大曲面
    let copyLargeFaceRet = await this.getIncrementServiceByName("copySurface", true, "", {
      surfaceIds: [this.modelParams.curveFace.elements[0].id],
      instanceIds: [""]
    })
    // (1)生成外框切割面
    let cutDatumFace1 = await this.getIncrementServiceByName("createPlaneByDistance", true, "", cutDatumPlaneParams)
    cutDatumPlaneParams.reverse = 1;
    let cutDatumFace2 = await this.getIncrementServiceByName("createPlaneByDistance", true, "", cutDatumPlaneParams)
    // 切割曲面1
    let splitRet = await this.getIncrementServiceByName("cutSplit", true, "",
      {solidId: copyLargeFaceRet.entityId, cutSurfaceId: cutDatumFace1.entityId})
    // 选择对应面1
    let firstFace = this.getNeedFace(splitRet.elements.filter(ele => ele.type === ElementType.FACE), frameDatumOrigin, false)
    hideEntities.push(...splitRet.elements.filter(j => j.id !== firstFace.id).map(j => j.params.entityRefId));
    splitRet = await this.getIncrementServiceByName("cutSplit", true, "",
      {solidId: firstFace.entityId, cutSurfaceId: cutDatumFace2.entityId})
    // 选择对应面2 -- 外框面
    let secondFace = this.getNeedFace(splitRet.elements.filter(ele => ele.type === ElementType.FACE), frameDatumOrigin, false)
    hideEntities.push(...splitRet.elements.filter(j => j.id !== secondFace.id).map(j => j.params.entityRefId));
    // (2)生成内框切割面
    // 向内偏移最大曲面
    let offsetLargeFaceRet = await this.getIncrementServiceByName("offsetSurface", true, "", {
      surfaceIds: [this.modelParams.curveFace.elements[0].id],
      distance: this.modelParams.size.frameHeight,
      reverse: 0,
    })
    let frameDatumOriginRet = await this.getIncrementServiceByName("createPointByProjectionPoint", true, "", {
      referenceFaceId: offsetLargeFaceRet.elements.find(j => j.type === ElementType.FACE)?.id,
      referencePoint: new Point(frameDatumOrigin.x, frameDatumOrigin.y, frameDatumOrigin.z)
    })
    frameDatumOrigin = frameDatumOriginRet.elements[0].params.position;
    cutDatumPlaneParams.paralDis = this.modelParams.size.frameWidth / 2 + 20;

    // 切割曲面2
    let cutDatumFace3 = await this.getIncrementServiceByName("createPlaneByDistance", true, "", cutDatumPlaneParams)
    cutDatumPlaneParams.reverse = 0;
    let cutDatumFace4 = await this.getIncrementServiceByName("createPlaneByDistance", true, "", cutDatumPlaneParams)
    splitRet = await this.getIncrementServiceByName("cutSplit", true, "",
      {solidId: offsetLargeFaceRet.entityId, cutSurfaceId: cutDatumFace3.entityId})
    let thirdFace = this.getNeedFace(splitRet.elements.filter(ele => ele.type === ElementType.FACE), frameDatumOrigin, false)
    hideEntities.push(...splitRet.elements.filter(j => j.id !== thirdFace.id).map(j => j.params.entityRefId));
    splitRet = await this.getIncrementServiceByName("cutSplit", true, "",
      {solidId: thirdFace.entityId, cutSurfaceId: cutDatumFace4.entityId})
    let fourthFace = this.getNeedFace(splitRet.elements.filter(ele => ele.type === ElementType.FACE), frameDatumOrigin, false)
    hideEntities.push(...splitRet.elements.filter(j => j.id !== fourthFace.id).map(j => j.params.entityRefId));

    // (3)获取长桁计算点
    // 需要把所有点投影到框基准面上再计算
    let trussCalPoint = null;
    let sPoint = this.modelParams.sDatum.elements[0].params.origin;
    sPoint = this.getProjectPointToFace(this.modelParams.frameDatum.elements[0].params, new Vector3(sPoint.x, sPoint.y, sPoint.z))

    let sDatum = this.modelParams.sDatum.elements[0].params;

    let ePoint = this.modelParams.eDatum.elements[0].params.origin;
    ePoint = this.getProjectPointToFace(this.modelParams.frameDatum.elements[0].params, new Vector3(ePoint.x, ePoint.y, ePoint.z))

    let eDatum = this.modelParams.eDatum.elements[0].params;
    if (this.modelParams.trussDatums.elements.length === 0) {
      // 没有长桁时，利用包框开始和结束面确定
      trussCalPoint = new Vector3((sPoint.x + ePoint.x) / 2, (sPoint.y + ePoint.y) / 2, (sPoint.z + ePoint.z) / 2)
    } else {
      let length = this.modelParams.trussDatums.elements.length;
      let x = 0, y = 0, z = 0;
      for (const ele of this.modelParams.trussDatums.elements) {
        let point = ePoint = this.getProjectPointToFace(this.modelParams.frameDatum.elements[0].params,
          new Vector3(ele.params.origin.x, ele.params.origin.y, ele.params.origin.z))
        x += point.x;
        y += point.y;
        z += point.z;
      }
      trussCalPoint = new Vector3(x / length, y / length, z / length)
    }

    // (4)裁剪外框
    splitRet = await this.getIncrementServiceByName("cutSplit", true, "",
      {solidId: secondFace.entityId, cutSurfaceId: sDatum.entityRefId});
    let fifthFace = this.getNeedFace(splitRet.elements.filter(ele => ele.type === ElementType.FACE), trussCalPoint, false)
    hideEntities.push(...splitRet.elements.filter(j => j.id !== fifthFace.id).map(j => j.params.entityRefId));
    splitRet = await this.getIncrementServiceByName("cutSplit", true, "",
      {solidId: fifthFace.entityId, cutSurfaceId: eDatum.entityRefId});
    let sixthFace = this.getNeedFace(splitRet.elements.filter(ele => ele.type === ElementType.FACE), trussCalPoint, false)
    hideEntities.push(...splitRet.elements.filter(j => j.id !== sixthFace.id).map(j => j.params.entityRefId));

    // (5)裁剪内框
    splitRet = await this.getIncrementServiceByName("cutSplit", true, "",
      {solidId: fourthFace.entityId, cutSurfaceId: sDatum.entityRefId});
    let sevenFace = this.getNeedFace(splitRet.elements.filter(ele => ele.type === ElementType.FACE), trussCalPoint, false)
    hideEntities.push(...splitRet.elements.filter(j => j.id !== sevenFace.id).map(j => j.params.entityRefId));
    splitRet = await this.getIncrementServiceByName("cutSplit", true, "",
      {solidId: sevenFace.entityId, cutSurfaceId: eDatum.entityRefId});
    let eightFace = this.getNeedFace(splitRet.elements.filter(ele => ele.type === ElementType.FACE), trussCalPoint, false)
    hideEntities.push(...splitRet.elements.filter(j => j.id !== eightFace.id).map(j => j.params.entityRefId));


    // (6)放样曲面
    let sixEdges = await this.getQueryServiceByType("getElementsByEntityId", sixthFace.entityId, [ElementType.EDGE])
    let eightEdges = await this.getQueryServiceByType("getElementsByEntityId", eightFace.entityId, [ElementType.EDGE])
    // 排除掉外框包框起始边
    let lines = sixEdges.allElements.filter(line => line.lineLength > this.modelParams.size.frameWidth * 1 + 10)
    // 两两放样曲面
    let surfaces = [eightFace.params.entityRefId];
    for (const line of lines) {
      let eightLine = this.getNearestLineByMiddlePoint(line.mPoint, eightEdges)
      let face = await this.getIncrementServiceByName("loftSurface", true, "", {
        profiles: [line.id, eightLine.id],
        hasGuideCurve: 0,
        guideCurves: []
      });
      surfaces.push(face.entityId)
    }

    // (8)缝合曲面
    let knifSurface = await this.getIncrementServiceByName("knitSurface", true, "", {
      surfaces,
    })

    // (9)加厚
    let solidRet = await this.getIncrementServiceByName("thicken", true, "包框模型", {
      thickenSurfaceId: knifSurface.entityId, thickness: this.modelParams.size.frameBorder, outward: 1
    })

    hideEntities.push(...[cutDatumFace1.entityId, cutDatumFace2.entityId, cutDatumFace3.entityId, cutDatumFace4.entityId]);
    await this.getIncrementServiceByName("setEntityVisible", false, "", {
      entityIds: [...new Set(hideEntities)],
      status: 0
    })

    let copyOutsideFaceRet = [];
    return {solidRet, copyOutsideFaceRet};
  }

  /**
   * 控制包框模型生成方式
   * @returns {Promise<void>}
   */
  async createTheFrame() {
    this.openLoading("正在生成包框隔热隔声层...")
    try {
      // 验证数据
      this.validData();
      // 删除旧数据，重新生成新数据
      await this.deleteOldData()
      // 设置起点，原点默认的 entitiyId为 50
      await this.getIncrementServiceByName("createPointByPoint", false, "起点", {
        referencePointId: 50
      })
      // 生成包框
      await this.createFrame();
      // 设置终点
      await this.getIncrementServiceByName("createPointByPoint", false, "终点", {
        referencePointId: 50
      })
      this.closeLoading()
      this.eventBusAlertMsg("生成完成", "success")
    } catch (e) {
      this.closeLoading()
      this.eventBusAlertMsg("生成失败,请检查参数有效性", "warning")
    }
  }

  /**
   * 删除原数据
   * @returns {Promise<void>}
   */
  async deleteOldData() {
    let featuresRet = await this.request.execFunction(PluginCADToolFunctionType.GETALLFEATURES, {})
    if (featuresRet.code === 200) {
      let startIndex = featuresRet.data.findIndex(j => j.name === "起点");
      let endIndex = featuresRet.data.findLastIndex(j => j.name === "终点");
      if (startIndex < 0) {
        return
      }
      if (endIndex < 0) {
        endIndex = featuresRet.data.length - 1;
      }
      let ids = [];
      for (let i = startIndex; i <= endIndex; i++) {
        ids.push(featuresRet.data[i].id)
      }
      await this.request.execFunction(PluginCADToolFunctionType.DELETEFEATURES, ids);
    }
  }

  /**
   * 验证界面元素
   */
  validData() {
    if (this.modelParams.curveFace.elements.length === 0) {
      throw "请选择贴合曲面";
    }
    if (this.modelParams.frameDatum.elements.length === 0) {
      throw "请选择框占位面";
    }
    if (this.modelParams.sDatum.elements.length === 0) {
      throw "请选择包框开始面";
    }
    if (this.modelParams.eDatum.elements.length === 0) {
      throw "请选择包框结束面";
    }
  }

  /**
   * 根据面元素获取三角面片
   * @param faces
   */
  getTrianglesByFace(faces) {
    faces.forEach(face => {
      face.points = []
      let count = face.params.index.length / 3
      let index = face.params.index;
      let pos = face.params.position;
      for (let i = 0; i < count; i++) {
        let index1 = index[i * 3];
        let index2 = index[i * 3 + 1];
        let index3 = index[i * 3 + 2];
        let point1 = {
          x: pos[3 * index1],
          y: pos[3 * index1 + 1],
          z: pos[3 * index1 + 2]
        }
        let point2 = {
          x: pos[3 * index2],
          y: pos[3 * index2 + 1],
          z: pos[3 * index2 + 2]
        }
        let point3 = {
          x: pos[3 * index3],
          y: pos[3 * index3 + 1],
          z: pos[3 * index3 + 2]
        }
        face.points.push({point1, point2, point3})
      }
    })
  }

  /**
   * 获取需要使用的平面
   * @param faces
   * @param targetPoint
   * @param isMax
   * @returns {*}
   */
  getNeedFace(faces, targetPoint, isMax) {
    // 计算点到面的位置
    let targetFace = null;
    let distances = [];
    faces.forEach(face => {
      let faceDistance = [];
      face.points.forEach(points =>
        faceDistance.push(this.getDistanceToTriangle(points.point1, points.point2, points.point3, targetPoint))
      )
      face.minDistance = Math.min(...faceDistance)
      distances.push(face.minDistance)
    })
    if (isMax) {
      // 返回距离最大的面
      targetFace = faces.find(face => face.minDistance === Math.max(...distances))
    } else {
      // 返回距离最小的面
      targetFace = faces.find(face => face.minDistance === Math.min(...distances))
    }
    if (!targetFace) {
      throw "生成失败"
    }
    return targetFace;
  }

  /**
   * 计算点到三角面片的距离
   * @param pointA
   * @param pointB
   * @param pointC
   * @param target
   * @returns {number}
   */
  getDistanceToTriangle(pointA, pointB, pointC, target) {
    // 定义点
    let point1 = new Vector3(pointA.x, pointA.y, pointA.z)
    let point2 = new Vector3(pointB.x, pointB.y, pointB.z)
    let point3 = new Vector3(pointC.x, pointC.y, pointC.z)
    let pointTarget = new Vector3(target.x, target.y, target.z)

    // 计算三角形的两条边向量
    let edge1 = point2.clone().sub(point1);
    let edge2 = point3.clone().sub(point1);

    // 计算目标点到三角形第一个顶点的向量
    let targetToVertex = pointTarget.clone().sub(point1);

    // 计算边向量叉积，得到三角形法向量
    let normal = new Vector3().crossVectors(edge1, edge2).normalize();

    // 计算目标点到三角形所在平面的距离
    let distanceToPlane = targetToVertex.dot(normal);

    // 计算投影点
    let projectPoint = pointTarget.clone().sub(normal.clone().multiplyScalar(distanceToPlane));

    // 检查投影点是否在三角形内部
    if (this.isPointInTriangle(point1, point2, point3, projectPoint)) {
      return Math.abs(distanceToPlane);
    } else {
      let distanceToEdge1 = this.distanceToLineSegment(point1, point2, pointTarget)
      let distanceToEdge2 = this.distanceToLineSegment(point2, point3, pointTarget)
      let distanceToEdge3 = this.distanceToLineSegment(point3, point1, pointTarget)
      // 返回最短距离
      return Math.min(distanceToEdge1, distanceToEdge2, distanceToEdge3)
    }
  }

  /**
   * 计算点的投影是否在三角形内部
   * @param point1
   * @param point2
   * @param point3
   * @param point
   * @returns {boolean}
   */
  isPointInTriangle(point1, point2, point3, point) {
    let v0 = point3.clone().sub(point1);
    let v1 = point2.clone().sub(point1);
    let v2 = point.clone().sub(point1);

    let dot00 = v0.dot(v0);
    let dot01 = v0.dot(v1);
    let dot02 = v0.dot(v2);
    let dot11 = v1.dot(v1);
    let dot12 = v1.dot(v2);

    let invDenom = 1 / (dot00 * dot11 - dot01 * dot01);
    let u = (dot11 * dot02 - dot01 * dot12) * invDenom;
    let v = (dot00 * dot12 - dot01 * dot02) * invDenom;

    return (u >= 0) && (v >= 0) && (u + v <= 1);
  }

  /**
   * 计算点到边的距离
   * @param start
   * @param end
   * @param point
   * @returns {*}
   */
  distanceToLineSegment(start, end, point) {
    // 类型判断
    if (!(start instanceof Vector3)) {
      start = new Vector3(start.x, start.y, start.z)
    }
    if (!(end instanceof Vector3)) {
      end = new Vector3(end.x, end.y, end.z)
    }
    if (!(point instanceof Vector3)) {
      point = new Vector3(point.x, point.y, point.z)
    }
    // 距离计算
    let lineLengthSquared = start.distanceToSquared(end);
    if (lineLengthSquared === 0) {
      return point.distanceTo(start);
    }
    let t = ((point.x - start.x) * (end.x - start.x) + (point.y - start.y) * (end.y - start.y)
      + (point.z - start.z) * (end.z - start.z)) / lineLengthSquared;
    t = Math.max(0, Math.min(1, t));
    let projection = new Vector3(start.x + t * (end.x - start.x), start.y + t * (end.y - start.y),
      start.z + t * (end.z - start.z))
    return point.distanceTo(projection);
  }

  /**
   * 计算到中点的最短距离
   * @param targetPoint
   * @param lines
   * @returns {T}
   */
  getNearestLineByMiddlePoint(targetPoint, lines) {
    let l = new Vector3(targetPoint.x, targetPoint.y, targetPoint.z);
    lines.allElements.forEach(j => {
      let m1 = new Vector3(j.mPoint.x, j.mPoint.y, j.mPoint.z);
      j.distance = m1.distanceTo(l)
    })
    return lines.allElements.find(l => l.distance === Math.min(...lines.allElements.map(line => line.distance)))
  }

  /**
   * 点的投影到平面
   * @param ele
   * @param pointTarget
   * @returns {*}
   */
  getProjectPointToFace(ele, pointTarget) {
    let faceNormal = new Vector3(ele.normal.x, ele.normal.y, ele.normal.z).normalize();
    let facePoint = new Vector3(ele.origin.x, ele.origin.y, ele.origin.z);
    let targetToVertex = pointTarget.clone().sub(facePoint);
    let distanceToPlane = targetToVertex.dot(faceNormal);
    let projectPoint = pointTarget.clone().sub(faceNormal.clone().multiplyScalar(distanceToPlane));
    return projectPoint;
  }

}

const wrapFramesDesignCommand = new WrapFramesDesignCommand();
export {wrapFramesDesignCommand};